#!/usr/bin/env python

import sys
import os
import datetime
import platform
import shutil
import time
import re
import threading
from subprocess import Popen, PIPE

CHECKSUM_OUT = "checksum.exe sc1086.hex CRC32"

# IAR path
iar_build_exe = "D:/Program Files (x86)/IAR Systems/Embedded Workbench 8.0/common/bin/IarBuild.exe"
iar_build_log = "cat ./project/build.log"

# output files path
iar_source_file_hex = "project/Debug/Exe/sc1086.hex"
iar_source_file_bin = "project/Debug/Exe/sc1086.bin"
target_path = "user/output"

# openocd define
stlink_config_file = "user/openocd/stlink-dap.cfg"
chip_config_file = "user/openocd/stm8l.cfg"
program_cmd = "\"init; load_image user/output/sc1086.hex 0x8000; verify; reset; shutdown\""

# STVP_CmdLine.exe define
stvp_cmd_Line           = "STVP_CmdLine.exe"
stvp_cmd_Line_rm_log    = "rm ./Result.log"
stvp_cmd_Line_print_log = "cat ./Result.log"

# 动画字符
ANIMATION_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']

class BuildAnimation:
    def __init__(self, message="Building"):
        self.message = message
        self.running = False
        self.thread = None
        self.current_char = 0

    def start(self):
        self.running = True
        self.thread = threading.Thread(target=self._animate)
        self.thread.daemon = True
        self.thread.start()

    def stop(self):
        self.running = False
        if self.thread:
            self.thread.join()
        # 清除动画行
        print('\r' + ' ' * (len(self.message) + 10) + '\r', end='', flush=True)

    def _animate(self):
        while self.running:
            char = ANIMATION_CHARS[self.current_char]
            print(f'\r{char} {self.message}...', end='', flush=True)
            time.sleep(0.1)
            self.current_char = (self.current_char + 1) % len(ANIMATION_CHARS)

def get_exitcode_stdout_stderr(args):
    """
    Execute the external command and get its exitcode, stdout and stderr.
    """
    proc = Popen(args, stdout=PIPE, stderr=PIPE, shell=True)
    out, err = proc.communicate()
    exitcode = proc.returncode
    return exitcode, out, err

def cp_build_file(source, target):
    assert not os.path.isabs(source)

    try:
        shutil.copy(source, target)
    except IOError as e:
        print("Unable to copy file. %s" % e)
    except:
        print("Unexpected error:", sys.exc_info())

def parse_memory_usage(log_content):
    """解析编译日志中的内存使用情况"""
    memory_info = {
        'code': 0,
        'const': 0,
        'data': 0,
        'total_code': 0,
        'total_const': 0,
        'total_data': 0
    }

    # 解析每个文件的编译信息 - 匹配不同格式
    code_patterns = [
        r'(\d+)\s+bytes of CODE\s+memory',
        r'(\d+)\s+bytes of CODE\s+\s+memory'  # 匹配双空格的情况
    ]
    const_patterns = [
        r'(\d+)\s+bytes of CONST\s+memory',
        r'(\d+)\s+bytes of CONST\s+\s+memory'
    ]
    data_patterns = [
        r'(\d+)\s+bytes of DATA\s+memory',
        r'(\d+)\s+bytes of DATA\s+\s+memory'
    ]

    # 收集所有匹配项
    code_matches = []
    const_matches = []
    data_matches = []

    for pattern in code_patterns:
        code_matches.extend(re.findall(pattern, log_content))
    for pattern in const_patterns:
        const_matches.extend(re.findall(pattern, log_content))
    for pattern in data_patterns:
        data_matches.extend(re.findall(pattern, log_content))

    # 计算总和
    memory_info['code'] = sum(int(x) for x in code_matches)
    memory_info['const'] = sum(int(x) for x in const_matches)
    memory_info['data'] = sum(int(x) for x in data_matches)

    # 解析链接器输出的总内存使用情况 - 匹配带空格的数字
    total_patterns = [
        r'(\d+)\s+bytes of readonly\s+code memory',
        r'(\d+)\s+(\d+)\s+bytes of readonly\s+code memory',  # 匹配带空格的数字如 "6 995"
        r'(\d+)\s+bytes of readonly\s+data memory',
        r'(\d+)\s+(\d+)\s+bytes of readonly\s+data memory',
        r'(\d+)\s+bytes of readwrite data memory',
        r'(\d+)\s+(\d+)\s+bytes of readwrite data memory'
    ]

    # 查找最后的总内存使用情况（最新的编译结果）
    lines = log_content.split('\n')
    for line in reversed(lines):
        # 匹配 readonly code memory
        match = re.search(r'(\d+)\s*(\d+)?\s+bytes of readonly\s+code memory', line)
        if match and memory_info['total_code'] == 0:
            if match.group(2):
                memory_info['total_code'] = int(match.group(1)) * 1000 + int(match.group(2))
            else:
                memory_info['total_code'] = int(match.group(1))

        # 匹配 readonly data memory
        match = re.search(r'(\d+)\s*(\d+)?\s+bytes of readonly\s+data memory', line)
        if match and memory_info['total_const'] == 0:
            if match.group(2):
                memory_info['total_const'] = int(match.group(1)) * 1000 + int(match.group(2))
            else:
                memory_info['total_const'] = int(match.group(1))

        # 匹配 readwrite data memory
        match = re.search(r'(\d+)\s*(\d+)?\s+bytes of readwrite data memory', line)
        if match and memory_info['total_data'] == 0:
            if match.group(2):
                memory_info['total_data'] = int(match.group(1)) * 1000 + int(match.group(2))
            else:
                memory_info['total_data'] = int(match.group(1))

    return memory_info

def print_memory_usage(memory_info):
    """打印内存使用情况"""
    print("\n" + "="*60)
    print("📊 内存使用情况统计")
    print("="*60)

    # STM8L051F3 内存规格
    FLASH_SIZE = 8 * 1024  # 8KB
    RAM_SIZE = 1 * 1024    # 1KB

    if memory_info['total_code'] > 0:
        code_usage = (memory_info['total_code'] / FLASH_SIZE) * 100
        const_usage = (memory_info['total_const'] / FLASH_SIZE) * 100
        data_usage = (memory_info['total_data'] / RAM_SIZE) * 100

        print(f"🔹 Flash 使用情况:")
        print(f"   - 代码段: {memory_info['total_code']:6d} bytes ({code_usage:5.1f}% of {FLASH_SIZE//1024}KB)")
        print(f"   - 常量段: {memory_info['total_const']:6d} bytes ({const_usage:5.1f}% of {FLASH_SIZE//1024}KB)")
        print(f"   - Flash总计: {memory_info['total_code'] + memory_info['total_const']:6d} bytes ({(code_usage + const_usage):5.1f}% of {FLASH_SIZE//1024}KB)")

        print(f"🔹 RAM 使用情况:")
        print(f"   - 数据段: {memory_info['total_data']:6d} bytes ({data_usage:5.1f}% of {RAM_SIZE//1024}KB)")

        # 显示进度条
        print(f"\n📈 Flash 使用率: ", end='')
        flash_total_usage = code_usage + const_usage
        bar_length = 30
        filled_length = int(bar_length * flash_total_usage / 100)
        bar = '█' * filled_length + '░' * (bar_length - filled_length)
        print(f"[{bar}] {flash_total_usage:5.1f}%")

        print(f"📈 RAM 使用率:   ", end='')
        filled_length = int(bar_length * data_usage / 100)
        bar = '█' * filled_length + '░' * (bar_length - filled_length)
        print(f"[{bar}] {data_usage:5.1f}%")

        # 警告信息
        if flash_total_usage > 90:
            print(f"\n⚠️  警告: Flash 使用率过高 ({flash_total_usage:.1f}%)")
        if data_usage > 90:
            print(f"\n⚠️  警告: RAM 使用率过高 ({data_usage:.1f}%)")
    else:
        print("❌ 无法获取内存使用信息")

def iar_build():
    print("🔨 编译器类型: IAR")

    # 启动动画
    animation = BuildAnimation("正在编译项目")
    animation.start()

    try:
        # 执行编译
        exitcode, out, err = get_exitcode_stdout_stderr([iar_build_exe, "project/sc1086.ewp", "-build", "Debug", "-log", "info", ">", "project/build.log"])

        # 停止动画
        animation.stop()

        # 显示编译日志
        os.system(iar_build_log)

        # 读取详细编译日志并解析内存使用情况
        try:
            with open("project/BuildLog.log", "r", encoding='utf-8', errors='ignore') as f:
                log_content = f.read()
                memory_info = parse_memory_usage(log_content)
                print_memory_usage(memory_info)
        except Exception as e:
            print(f"⚠️  无法解析内存使用信息: {e}")

        # 复制输出文件
        cp_build_file(iar_source_file_hex, target_path)

        if exitcode == 0:
            print("✅ 编译成功!")
        else:
            print("❌ 编译失败!")
            return False

    except Exception as e:
        animation.stop()
        print(f"❌ 编译过程中出现错误: {e}")
        return False

    return True

def sdcc_build():
    print("🔨 编译器类型: SDCC")

    # 启动动画
    animation = BuildAnimation("正在编译项目")
    animation.start()

    try:
        # 执行编译
        exitcode = os.system('make')

        # 停止动画
        animation.stop()

        if exitcode == 0:
            print("✅ 编译成功!")
        else:
            print("❌ 编译失败!")
            return False

    except Exception as e:
        animation.stop()
        print(f"❌ 编译过程中出现错误: {e}")
        return False

    return True

def stlink_download():
    host_os = platform.system()
    print(f"🖥️  主机系统: {host_os}")

    if host_os == 'Windows':
        # 启动动画
        animation = BuildAnimation("正在下载程序")
        animation.start()

        try:
            os.system(stvp_cmd_Line_rm_log)
            exitcode, out, err = get_exitcode_stdout_stderr([stvp_cmd_Line, "-BoardName=ST-LINK", "-ProgMode=SWIM", "-Tool_ID=0", \
                                           "-Device=STM8L051x3", "-log", "-verbose", "-progress", "-no_loop", \
                                           "-FileProg=./user/output/sc1086.hex"])

            # 停止动画
            animation.stop()

            os.system(stvp_cmd_Line_print_log)

            if exitcode == 0:
                print("✅ 下载成功!")
            else:
                print("❌ 下载失败!")
                return False

        except Exception as e:
            animation.stop()
            print(f"❌ 下载过程中出现错误: {e}")
            return False
    else:
        print("❌ 不支持的操作系统")
        sys.exit(2)

    return True

def build_select(para):
    if para == 'iar':
        return iar_build()
    elif para == 'sdcc':
        return sdcc_build()
    elif para == 'stlink':
        return stlink_download()
    else:
        print("❌ 输入参数错误!")
        return False

def checksum_hex_file():
    print("\n🔍 正在计算校验和...")
    # 切换目录到
    os.chdir(target_path)
    # 输出文件需要的信息
    os.system(CHECKSUM_OUT)

def main_func(parameter):
    start = datetime.datetime.now()

    print("🚀 开始构建项目...")
    print(f"⏰ 开始时间: {start.strftime('%Y-%m-%d %H:%M:%S')}")
    print("-" * 60)

    success = build_select(parameter)

    if success:
        print("\n" + "-" * 60)
        checksum_hex_file()

    end = datetime.datetime.now()
    duration = end - start

    print("\n" + "="*60)
    print(f"⏱️  总耗时: {duration.total_seconds():.2f} 秒")
    print(f"⏰ 结束时间: {end.strftime('%Y-%m-%d %H:%M:%S')}")

    if success:
        print("🎉 构建完成!")
    else:
        print("💥 构建失败!")
        sys.exit(1)

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("❌ 用法: python build.py [iar|sdcc|stlink]")
        sys.exit(1)
    main_func(sys.argv[1])